using Digitalmes.Application.Scada.Models;
using Digitalmes.Application.Services;
using Digitalmes.Domain.Entities.Mes.Processes;
using Digitalmes.Domain.Entities.Mes.Productions;

namespace Digitalmes.Application.Scada.Services.Impl;

/// <summary>
/// 关键物料扫码服务。
/// </summary>
internal sealed class ScanKeyService(
    IRepository<ProdCurrentState> currentStateRepo,
    IRepository<ProcRoutingStep> routeStepRepo,
    IRepository<ProdMaterialConsumption> materialConsumptionRepo,
    IWorkstationQueryService workstationQueryService,
    IMaterialMatchService materialCheckService) : IScanKeyService
{
    public async Task<ScadaResult> ScanAsync(ScanKeyInput input, CancellationToken cancellationToken = default)
    {
        // 检索工序信息
        var (operation, workstation) = await workstationQueryService.GetOperationAsync(input.LineCode, input.WsCode);
        if (operation == null)
        {
            return ScadaResult.Error(ErrorCode.E1403);
        }

        // 找到产品在当前工序信息
        var currentState = await currentStateRepo.FirstOrDefaultAsync(m => m.Sn == input.SN && m.LineId == operation.LineId && m.OperationId == operation.Id, cancellationToken);
        if (currentState == null)
        {
            return ScadaResult.Error(ErrorCode.E1404);
        }

        if (currentState.Stage is WorkStageEnum.Out)
        {
            return ScadaResult.Error(ErrorCode.E1405);
        }

        // 找到工艺步骤及其用料设定
        var routeStep = await routeStepRepo.AsQueryable()
            .Includes(m => m.Materials, x => x.Material)
            .FirstOrDefaultAsync(m => m.RoutingId == currentState.RoutingId && m.OperationId == currentState.OperationId, cancellationToken);
        if (routeStep!.Materials.Count == 0)
        {
            return ScadaResult.Error(ErrorCode.E1406);
        }

        // 通过条码规则，找到对应的用料信息
        // 不设置时默认上料顺序 seq 为 0
        var matchFunc = await materialCheckService.GetMatchFuncAsync();
        var stepMaterial = routeStep.Materials.FirstOrDefault(m => m.Material != null && m.Material.Attr == MaterialAttrEnum.Key
                && m.Seq == input.Step
                && matchFunc(input.Barcode, m.Material.Rules));
        if (stepMaterial == null)
        {
            return ScadaResult.Error(input.Step > 0 ? ErrorCode.E1408 : ErrorCode.E1407);
        }

        // 检查物料来源，检查该物料作为产品在指定产线（若有设置来源产线）是否有良品完工
        if (stepMaterial.IsCheckSource)
        {
            var progress2 = await currentStateRepo.AsQueryable()
                .WhereIF(stepMaterial.SourceLineIds?.Length > 0, m => stepMaterial.SourceLineIds!.Contains(m.LineId))
                .FirstOrDefaultAsync(m => m.Sn == input.Barcode, cancellationToken);
            if (progress2 == null)
            {
                return ScadaResult.Error(ErrorCode.E1412);
            }

            // 良品完工
            if (!progress2.IsCompelted || progress2.IsGood())
            {
                return ScadaResult.Error(ErrorCode.E1413);
            }
        }

        // 检查未解绑的物料是否已被使用（或是在其他产线或其他工位）
        var consumption = await materialConsumptionRepo.FirstOrDefaultAsync(m => m.Barcode == input.Barcode && !m.IsUnbound, cancellationToken);
        if (consumption != null)
        {
            // 本工位重复扫码
            if (consumption.Sn == currentState.Sn && consumption.WorkstationId == currentState.WorkstationId)
            {
                return ScadaResult.Error(ErrorCode.E1410);
            }

            // 在其他工位已被使用
            return ScadaResult.Error(ErrorCode.E1409);
        }

        // 检查在此工序中该产品的该物料是否有同种物料已经上料
        var consumption2 = await materialConsumptionRepo.GetListAsync(m => m.Sn == input.SN
                && m.LineId == currentState.LineId
                && m.OperationId == currentState.OperationId
                && m.MaterialId == stepMaterial.MaterialId
                && m.MaterialType == MaterialAttrEnum.Key
                && !m.IsUnbound, cancellationToken);
        if (consumption2.Count == stepMaterial.Qty)
        {
            // 此种物料在该工序已全部上料
            return ScadaResult.Error(ErrorCode.E1411);
        }

        // 新增上料记录
        ProdMaterialConsumption materialConsumption = new()
        {
            Sn = currentState.Sn,
            LineId = currentState.LineId,
            LineCode = workstation?.Line?.LineCode,
            OperationId = currentState.OperationId,
            OperationCode = currentState.OperationCode,
            WorkstationId = currentState.WorkstationId,
            WorkstationCode = currentState.WorkstationCode,
            Step = input.Step,
            MaterialId = stepMaterial.MaterialId,
            MaterialCode = stepMaterial.Material?.Code,
            Barcode = input.Barcode,
            MaterialType = MaterialAttrEnum.Key,
            LoadingTime = DateTime.Now,
        };
        await materialConsumptionRepo.InsertAsync(materialConsumption, cancellationToken);

        return ScadaResult.Ok();
    }
}
